import { ToolbarItem } from "@/components";
import { i18n } from "@/locales";
import { EditorState } from "@/tiptap/pm";
import { Editor, Extension } from "@/tiptap/vue-3";
import type { ExtensionOptions } from "@/types";
import { h, markRaw, render } from "vue";
import MdiTextBoxSearchOutline from "~icons/mdi/text-box-search-outline";
import SearchAndReplaceVue from "./SearchAndReplace.vue";
import {
  SearchAndReplacePlugin,
  searchAndReplacePluginKey,
} from "./SearchAndReplacePlugin";

declare module "@/tiptap" {
  interface Commands<ReturnType> {
    searchAndReplace: {
      /**
       * @description Replace first instance of search result with given replace term.
       */
      replace: () => ReturnType;
      /**
       * @description Replace all instances of search result with given replace term.
       */
      replaceAll: () => ReturnType;
      /**
       * @description Find next instance of search result.
       */
      findNext: () => ReturnType;
      /**
       * @description Find previous instance of search result.
       */
      findPrevious: () => ReturnType;
      /**
       * @description Open search panel.
       */
      openSearch: () => ReturnType;
      /**
       * @description Close search panel.
       */
      closeSearch: () => ReturnType;
    };
  }
}

// 使用 WeakMap 为每个编辑器实例存储独立的查找替换组件实例
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const editorInstances = new WeakMap<Editor, any>();

function getOrCreateInstance(editor: Editor) {
  let instance = editorInstances.get(editor);
  if (!instance) {
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    instance = h<any>(SearchAndReplaceVue);
    editorInstances.set(editor, instance);
  }
  return instance;
}

function isShowSearch(editor: Editor) {
  const instance = editorInstances.get(editor);
  if (instance) {
    const searchAndReplaceInstance = instance.component;
    if (searchAndReplaceInstance) {
      return searchAndReplaceInstance.props.visible;
    }
  }
  return false;
}

const SearchAndReplace = Extension.create<ExtensionOptions>({
  name: "searchAndReplace",

  // @ts-ignore
  addOptions() {
    return {
      getToolbarItems({ editor }: { editor: Editor }) {
        return [
          {
            priority: 230,
            component: markRaw(ToolbarItem),
            props: {
              editor,
              isActive: isShowSearch(editor),
              icon: markRaw(MdiTextBoxSearchOutline),
              title: i18n.global.t(
                "editor.extensions.search_and_replace.title"
              ),
              action: () => {
                const instance = editorInstances.get(editor);
                if (instance) {
                  const searchAndReplaceInstance = instance.component;
                  if (searchAndReplaceInstance) {
                    const visible = searchAndReplaceInstance.props.visible;
                    if (visible) {
                      editor.commands.closeSearch();
                    } else {
                      editor.commands.openSearch();
                    }
                  }
                }
              },
            },
          },
        ];
      },
    };
  },

  addCommands() {
    return {
      replace:
        () =>
          ({
            state,
            dispatch,
          }: {
            state: EditorState;
            // eslint-disable-next-line @typescript-eslint/no-explicit-any
            dispatch: ((args?: any) => any) | undefined;
          }) => {
            const searchAndReplaceState =
              searchAndReplacePluginKey.getState(state);
            if (!searchAndReplaceState) {
              return false;
            }
            const { replaceTerm, results, findIndex } = searchAndReplaceState;
            const result = results[findIndex];
            if (!result) {
              return false;
            }

            const { from, to } = result;

            if (dispatch) {
              const tr = state.tr;
              tr.insertText(replaceTerm, from, to);
              tr.setMeta(searchAndReplacePluginKey, {
                setFindIndex: findIndex,
                refresh: true,
              });
              dispatch(tr);
            }

            return false;
          },

      replaceAll:
        () =>
          ({
            state,
            dispatch,
          }: {
            state: EditorState;
            // eslint-disable-next-line @typescript-eslint/no-explicit-any
            dispatch: ((args?: any) => any) | undefined;
          }) => {
            const searchAndReplaceState =
              searchAndReplacePluginKey.getState(state);
            if (!searchAndReplaceState) {
              return false;
            }
            const { replaceTerm, results } = searchAndReplaceState;
            const tr = state.tr;
            let offset = 0;
            results.forEach((result) => {
              const { from, to } = result;
              tr.insertText(replaceTerm, offset + from, offset + to);
              // when performing multi-text replacement, it is necessary
              // to calculate the offset between 'form' and 'to'.
              offset = offset + replaceTerm.length - (to - from);
            });

            if (dispatch) {
              dispatch(tr);
            }
            return false;
          },

      findNext:
        () =>
          ({
            state,
            dispatch,
          }: {
            state: EditorState;
            // eslint-disable-next-line @typescript-eslint/no-explicit-any
            dispatch: ((args?: any) => any) | undefined;
          }) => {
            if (dispatch) {
              const tr = state.tr;
              const searchAndReplaceState =
                searchAndReplacePluginKey.getState(state);
              if (!searchAndReplaceState) {
                return false;
              }
              const { findIndex } = searchAndReplaceState;

              tr.setMeta(searchAndReplacePluginKey, {
                setFindIndex: findIndex + 1,
              });
              dispatch(tr);
            }
            return false;
          },

      findPrevious:
        () =>
          ({
            state,
            dispatch,
          }: {
            state: EditorState;
            // eslint-disable-next-line @typescript-eslint/no-explicit-any
            dispatch: ((args?: any) => any) | undefined;
          }) => {
            if (dispatch) {
              const searchAndReplaceState =
                searchAndReplacePluginKey.getState(state);
              if (!searchAndReplaceState) {
                return false;
              }
              const { findIndex } = searchAndReplaceState;
              const tr = state.tr;
              tr.setMeta(searchAndReplacePluginKey, {
                setFindIndex: findIndex - 1,
              });
              dispatch(tr);
            }
            return false;
          },

      openSearch:
        () =>
          ({
            state,
            dispatch,
            editor,
          }: {
            state: EditorState;
            // eslint-disable-next-line @typescript-eslint/no-explicit-any
            dispatch: ((args?: any) => any) | undefined;
            editor: Editor;
          }) => {
            const searchAndReplaceState =
              searchAndReplacePluginKey.getState(state);
            if (!searchAndReplaceState) {
              return false;
            }
            const instance = editorInstances.get(editor);
            if (instance) {
              const searchAndReplaceInstance = instance.component;
              if (searchAndReplaceInstance) {
                searchAndReplaceInstance.props.visible = true;
                const tr = state.tr;
                tr.setMeta(searchAndReplacePluginKey, {
                  setEnable: true,
                });
                if (dispatch) {
                  dispatch(tr);
                }
              }
            }
            return false;
          },

      closeSearch:
        () =>
          ({
            state,
            dispatch,
            editor,
          }: {
            state: EditorState;
            // eslint-disable-next-line @typescript-eslint/no-explicit-any
            dispatch: ((args?: any) => any) | undefined;
            editor: Editor;
          }) => {
            const searchAndReplaceState =
              searchAndReplacePluginKey.getState(state);
            if (!searchAndReplaceState) {
              return false;
            }
            const instance = editorInstances.get(editor);
            if (instance) {
              const searchAndReplaceInstance = instance.component;
              if (searchAndReplaceInstance) {
                searchAndReplaceInstance.props.visible = false;
                const tr = state.tr;
                tr.setMeta(searchAndReplacePluginKey, {
                  setEnable: false,
                });
                if (dispatch) {
                  dispatch(tr);
                }
              }
            }
            return false;
          },
    };
  },

  addProseMirrorPlugins() {
    const containerDom = document.createElement("div");
    containerDom.style.position = "sticky";
    containerDom.style.top = "0";
    containerDom.style.zIndex = "10000";
    containerDom.style.pointerEvents = "none";
    
    // 为当前编辑器实例获取或创建独立的查找替换组件实例
    const instance = getOrCreateInstance(this.editor as Editor);
    instance.props = {
      editor: this.editor,
      pluginKey: searchAndReplacePluginKey,
      visible: false,
    };
    render(instance, containerDom);
    return [
      SearchAndReplacePlugin({
        editor: this.editor as Editor,
        element: containerDom,
      }),
    ];
  },

  addKeyboardShortcuts() {
    return {
      "Mod-f": () => {
        this.editor.commands.openSearch();
        return true;
      },
    };
  },
});

export default SearchAndReplace;
